疫情時代,視訊串流當頭,用視訊鏡頭來做個線上攝影吧!
自從疫情爆發後,各行各業也開始進行居家辦公,使得視訊軟體及相關技術開始受重視,身為前端,我們也可以拿視訊鏡頭來做些好玩有趣的東西,而 ImageCapture 就是其中一個可以運用在這裡的 API。
在認識 ImageCapture 之前我們必須要先了解 MediaStream,瀏覽器將獲取的影音資訊稱之為「流」(Stream),其中流又包含了「軌」(Track),如影像軌、音訊軌,而我們可以透過向使用者獲取授權並透過裝置來取得這些影音資訊,進而達到我們想要的操作目的。
而我們要取得 MediaStream 的手段就是要先向使用者獲取設備的授權,這時候就要使用 getUserMedia
:
navigator.mediaDevices.getUserMedia({ video: true })
.then(mediaStream => {
/* use the stream */
})
.catch(err => {
/* handle the error */
});
當我們呼叫 getUserMedia
時,必須傳入一個稱為 constraints
的參數,該參數為一個物件,當中需要表示你想取得的 Track,例如上面我們就是傳入 { video: true }
,來取得視訊軌。
而 getUserMedia
會回傳 Promise 給我們,當使用者同意授權後就可以在 then
的 Callback 中取得 MediaStream。
當我們取得 MediaStream 後,我們還需要再取得當中的 Track,之後才可以透過 ImageCapture 來操作,這時候就需要使用 MediaStream 自身的 method getVideoTracks
。
navigator.mediaDevices.getUserMedia({ video: true })
.then(mediaStream => {
const videoTrack = mediaStream.getVideoTracks()[0];
})
.catch(err => {
console.log(err)
});
要注意的是,由於一個 MediaStream 中未必只有一個 VideoTrack,所以 getVideoTracks
回傳的會是陣列,記得要透過 index
索引出來。
知道如何取得 MediaStreamTrack 後,就可以來認識 ImageCapture 了,它可以讓我們建立一個圖像擷取器,只要提供一個有效的 VideoTrack 給 ImageCapture 就可以進行圖像的擷取:
const imageCapture = new ImageCapture(videoTrack);
當我們為一個 ImageCapture 綁定了 VideoTrack 後,我們就可以透過 ImageCapture 底下的 methods 來進行圖像擷取了:
let imageCapture;
navigator.mediaDevices.getUserMedia({ video: true })
.then(mediaStream => {
const videoTrack = mediaStream.getVideoTracks()[0];
imageCapture = new ImageCapture(videoTrack);
})
.catch(err => {
console.log(err)
});
document.querySelector("button").addEventListener("click", function() {
imageCapture.takePhoto().then(blob => {
console.log(blob)
});
})
呼叫 takePhoto
後,它會回傳 Promise,並且我們能在 then
的 Callback 中取得截圖的 Blob 物件。
再來要介紹的則是 grabFrame
,它和 takePhoto
一樣是擷取 videoTrack 的影像,差別在於它回傳的是 ImageBitmap 物件,而這種物件的好處是可以直接拿來畫在 Canvas 上。
imageCapture.takePhoto().then(blob => {
console.log(blob)
});
imageCapture.grabFrame().then(imageBitmap => {
console.log(imageBitmap)
});
Blob 物件可以夠過
createImageBitmap(blob)
來轉換成 ImageBitmap 物件
那最後我們就透過今天認識的 API 來實際做個視訊截圖攝影吧,首先先準備幾個按鈕以及 video
和 canvas
。
<div>
<button onclick="openCamera()">開啟鏡頭</button>
<button onclick="capture()">擷取畫面</button>
</div>
<video></video>
<canvas></canvas>
再來是在 openCamera
的時候使用 getUserMedia
及 getVideoTracks
來取得 MediaStreamTrack 並建立 ImageCapture。處此之外,我們還設定了 video.srcObject
,如此一來我們就可以夠過 <video>
標籤來預覽視訊畫面。
var video = document.querySelector('video');
var canvas = document.querySelector('canvas');
var context = canvas.getContext('2d');
var videoTrack;
var imageCapture;
// 開啟鏡頭
function openCamera() {
navigator.mediaDevices
.getUserMedia({ video: true })
.then((stream) => {
// 取得視訊軌並建立 imageCapture
videoTrack = stream.getVideoTracks()[0];
imageCapture = new ImageCapture(videoTrack);
// 將媒體流設定到 <video> 中顯示播放
video.srcObject = stream;
video.play();
})
.catch((err) => {
console.log(err);
});
}
//擷取畫面
function capture() {
imageCapture
.takePhoto()
.then(blob => {
// 將 Blob 轉成 ImageBitmap
return createImageBitmap(blob)
})
.then(imageBitmap => {
// 繪製在 canvas 上
const { width, height } = imageBitmap;
const ratio = video.videoWidth / width;
canvas.setAttribute('width', width * ratio);
canvas.setAttribute('height', height * ratio);
context.drawImage(imageBitmap, 0, 0, width * ratio, height * ratio);
});
}
最後只要在 capture
的時候透過 takePhoto
進行截圖,並將 Blob 轉成 ImageBitmap 後丟到 <canvas>
裡,就大功告成囉。
完整的 code 我就放在 這裡,大家可以看看實際效果。
其實視訊的操作沒有大家想像的那麼困難,簡單幾個 API 就可以做到,今天做的鏡頭截圖其實就可以做在會員的大頭照設定,讓使用者可以直接利用視訊鏡頭拍攝大頭照,相當便利。